/*
 * Copyright 2020-2021 by Nedim Sabic Sabic
 * https://www.fibratus.io
 * All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package event

import (
	"encoding/binary"
	"fmt"
	"github.com/rabbitstack/fibratus/pkg/event/params"
	"github.com/rabbitstack/fibratus/pkg/sys"
	"github.com/rabbitstack/fibratus/pkg/sys/etw"
	"github.com/rabbitstack/fibratus/pkg/util/filetime"
	"github.com/rabbitstack/fibratus/pkg/util/hashers"
	"github.com/rabbitstack/fibratus/pkg/util/hostname"
	"github.com/rabbitstack/fibratus/pkg/util/ntstatus"
	"golang.org/x/sys/windows"
	"os"
	"strings"
	"sync"
	"unsafe"
)

var (
	// DropCurrentProc determines if the events generated by the current, i.e. Fibratus process, are dropped
	DropCurrentProc = true
	// currentPid is the current process identifier
	currentPid = uint32(os.Getpid())
	// rundowns stores the hashes of processed rundown events
	rundowns = map[uint64]bool{}
	mu       sync.Mutex
)

// New constructs a fresh event instance with basic fields and parameters
// from the raw ETW event record.
func New(seq uint64, evt *etw.EventRecord) *Event {
	var (
		pid = evt.Header.ProcessID
		tid = evt.Header.ThreadID
		cpu = *(*uint8)(unsafe.Pointer(&evt.BufferContext.ProcessorIndex[0]))
		ts  = filetime.ToEpoch(evt.Header.Timestamp)
		typ = NewTypeFromEventRecord(evt)
	)

	e := &Event{
		Seq:         seq,
		PID:         pid,
		Tid:         tid,
		CPU:         cpu,
		Type:        typ,
		Category:    typ.Category(),
		Name:        typ.String(),
		Params:      make(map[string]*Param),
		Description: typ.Description(),
		Timestamp:   ts,
		Metadata:    make(map[MetadataKey]any),
		Host:        hostname.Get(),
	}

	e.produceParams(evt)
	e.adjustPID()

	return e
}

func (e *Event) adjustPID() {
	switch e.Category {
	case Image:
		// sometimes the pid present in event header is invalid
		// but, we can get the valid one from the event parameters
		if e.InvalidPid() {
			e.PID, _ = e.Params.GetPid()
		}
	case File:
		if !e.IsMapViewFile() && !e.IsUnmapViewFile() {
			// take thread id from the event parameters
			e.Tid, _ = e.Params.GetTid()
		}
		switch {
		case e.InvalidPid() && e.Type == MapFileRundown:
			// a valid pid for map rundown events
			// is located in the event parameters
			e.PID = e.Params.MustGetPid()
		case e.InvalidPid():
			// on some Windows versions the value of
			// the PID is invalid in the event header
			access := uint32(windows.THREAD_QUERY_LIMITED_INFORMATION)
			thread, err := windows.OpenThread(access, false, e.Tid)
			if err != nil {
				return
			}
			defer func() {
				_ = windows.CloseHandle(thread)
			}()
			e.PID = sys.GetProcessIdOfThread(thread)
		}
	case Process:
		// process start events may be logged in the context of the parent or child process.
		// As a result, the ProcessId member of EVENT_TRACE_HEADER may not correspond to the
		// process being created, so we set the event pid to be the one of the parent process
		if e.IsCreateProcess() {
			e.PID, _ = e.Params.GetPpid()
		}
	case Net:
		if !e.IsDNS() {
			e.PID, _ = e.Params.GetPid()
		}
	case Handle:
		if e.Type == DuplicateHandle {
			e.PID, _ = e.Params.GetUint32(params.TargetProcessID)
			e.Params.Remove(params.TargetProcessID)
		}
	case Thread:
		if e.Type == StackWalk {
			e.PID, _ = e.Params.GetPid()
			e.Tid, _ = e.Params.GetTid()
		}
	}
}

// IsDropped determines if the event should be dropped. The event
// is dropped in under the following circumstances:
//
// 1. The event is dealing with state management, and as long as
// we're not storing them into the capture file, it can be dropped
// 2. Rundowns events are dropped if they haven't been processed already
// 3. If the event is generated by Fibratus process, we can safely ignore it
func (e *Event) IsDropped(capture bool) bool {
	if e.IsState() && !capture {
		return true
	}
	if e.IsRundown() && e.IsRundownProcessed() {
		return true
	}
	return IsCurrentProcDropped(e.PID)
}

// IsCurrentProcDropped determines if the event originated from the
// current process is dropped.
func IsCurrentProcDropped(pid uint32) bool { return DropCurrentProc && pid == currentPid }

// DelayKey returns the value that is used to
// store and reference delayed events in the event
// backlog state. The delayed event is indexed by
// the sequence identifier.
func (e *Event) DelayKey() uint64 {
	switch e.Type {
	case CreateHandle, CloseHandle:
		return e.Params.MustGetUint64(params.HandleObject)
	}
	return 0
}

// IsNetworkTCP determines whether the event pertains to network TCP events.
func (e *Event) IsNetworkTCP() bool {
	return e.Category == Net && !e.IsNetworkUDP()
}

// IsNetworkUDP determines whether the event pertains to network UDP events.
func (e *Event) IsNetworkUDP() bool {
	return e.Type == RecvUDPv4 || e.Type == RecvUDPv6 || e.Type == SendUDPv4 || e.Type == SendUDPv6
}

// IsDNS determines whether the event is a DNS question/answer.
func (e *Event) IsDNS() bool {
	return e.Type.Subcategory() == DNS
}

// IsRundown determines if this is a rundown events.
func (e *Event) IsRundown() bool {
	return e.Type == ProcessRundown || e.Type == ThreadRundown || e.Type == ImageRundown ||
		e.Type == FileRundown || e.Type == RegKCBRundown
}

// IsSuccess checks if the event contains the status parameter
// and in such case, returns true if the operation completed
// successfully, i.e. the system code is equal to ERROR_SUCCESS.
func (e *Event) IsSuccess() bool {
	if !e.Params.Contains(params.NTStatus) {
		return true
	}
	return e.GetParamAsString(params.NTStatus) == ntstatus.Success
}

// IsRundownProcessed checks if the rundown events was processed
// to discard writing the snapshot state if the process/module is
// already present. This usually happens when we purposely alter
// the tracing session to induce the arrival of rundown events
// by calling into the `etw.SetTraceInformation` Windows API
// function which causes duplicate rundown events.
// For more pointers check `internal/etw/trace.go` and the
// `etw.SetTraceInformation` API function.
func (e *Event) IsRundownProcessed() bool {
	mu.Lock()
	defer mu.Unlock()
	key := e.RundownKey()
	_, isProcessed := rundowns[key]
	if isProcessed {
		return true
	}
	rundowns[key] = true
	return false
}

func (e *Event) IsCreateFile() bool             { return e.Type == CreateFile }
func (e *Event) IsCreateProcess() bool          { return e.Type == CreateProcess }
func (e *Event) IsCreateProcessInternal() bool  { return e.Type == CreateProcessInternal }
func (e *Event) IsCreateThread() bool           { return e.Type == CreateThread }
func (e *Event) IsCloseFile() bool              { return e.Type == CloseFile }
func (e *Event) IsCreateHandle() bool           { return e.Type == CreateHandle }
func (e *Event) IsCloseHandle() bool            { return e.Type == CloseHandle }
func (e *Event) IsDeleteFile() bool             { return e.Type == DeleteFile }
func (e *Event) IsEnumDirectory() bool          { return e.Type == EnumDirectory }
func (e *Event) IsTerminateProcess() bool       { return e.Type == TerminateProcess }
func (e *Event) IsTerminateThread() bool        { return e.Type == TerminateThread }
func (e *Event) IsUnloadImage() bool            { return e.Type == UnloadImage }
func (e *Event) IsLoadImage() bool              { return e.Type == LoadImage }
func (e *Event) IsLoadImageInternal() bool      { return e.Type == LoadImageInternal }
func (e *Event) IsImageRundown() bool           { return e.Type == ImageRundown }
func (e *Event) IsFileOpEnd() bool              { return e.Type == FileOpEnd }
func (e *Event) IsRegSetValue() bool            { return e.Type == RegSetValue }
func (e *Event) IsRegSetValueInternal() bool    { return e.Type == RegSetValueInternal }
func (e *Event) IsProcessRundown() bool         { return e.Type == ProcessRundown }
func (e *Event) IsProcessRundownInternal() bool { return e.Type == ProcessRundownInternal }
func (e *Event) IsVirtualAlloc() bool           { return e.Type == VirtualAlloc }
func (e *Event) IsMapViewFile() bool            { return e.Type == MapViewFile }
func (e *Event) IsUnmapViewFile() bool          { return e.Type == UnmapViewFile }
func (e *Event) IsStackWalk() bool              { return e.Type == StackWalk }

// InvalidPid indicates if the process generating the event is invalid.
func (e *Event) InvalidPid() bool { return e.PID == sys.InvalidProcessID }

// CurrentPid indicates if Fibratus is the process generating the event.
func (e *Event) CurrentPid() bool { return e.PID == currentPid }

// IsSystemPid indicates if the process generating the event is the System process.
func (e *Event) IsSystemPid() bool { return e.PID == 4 }

// IsState indicates if this event is only used for state management.
func (e *Event) IsState() bool { return e.Type.OnlyState() }

// IsCreateDisposition determines if the file disposition leads to creating a new file.
func (e *Event) IsCreateDisposition() bool {
	return e.IsCreateFile() && e.Params.MustGetUint32(params.FileOperation) == windows.FILE_CREATE
}

// IsOpenDisposition determines if the file disposition leads to opening a file object.
func (e *Event) IsOpenDisposition() bool {
	return e.IsCreateFile() && e.Params.MustGetUint32(params.FileOperation) == windows.FILE_OPEN
}

// StackID returns the integer that is used to identify the callstack present in the StackWalk event.
func (e *Event) StackID() uint64 { return uint64(e.PID + e.Tid) }

// RundownKey calculates the rundown event hash. The hash is
// used to determine if the rundown event was already processed.
func (e *Event) RundownKey() uint64 {
	switch e.Type {
	case ProcessRundown:
		b := make([]byte, 4)
		pid, _ := e.Params.GetPid()

		binary.LittleEndian.PutUint32(b, pid)

		return hashers.FnvUint64(b)
	case ThreadRundown:
		b := make([]byte, 8)
		pid, _ := e.Params.GetPid()
		tid, _ := e.Params.GetTid()

		binary.LittleEndian.PutUint32(b, pid)
		binary.LittleEndian.PutUint32(b, tid)

		return hashers.FnvUint64(b)
	case ImageRundown:
		pid, _ := e.Params.GetPid()
		mod, _ := e.Params.GetString(params.ImagePath)
		b := make([]byte, 4+len(mod))

		binary.LittleEndian.PutUint32(b, pid)
		b = append(b, mod...)

		return hashers.FnvUint64(b)
	case FileRundown:
		b := make([]byte, 8)
		fileObject, _ := e.Params.GetUint64(params.FileObject)
		binary.LittleEndian.PutUint64(b, fileObject)

		return hashers.FnvUint64(b)
	case MapFileRundown:
		b := make([]byte, 12)
		fileKey, _ := e.Params.GetUint64(params.FileKey)
		binary.LittleEndian.PutUint32(b, e.PID)
		binary.LittleEndian.PutUint64(b, fileKey)

		return hashers.FnvUint64(b)
	case RegKCBRundown:
		key, _ := e.Params.GetString(params.RegPath)
		b := make([]byte, 4+len(key))

		binary.LittleEndian.PutUint32(b, e.PID)
		b = append(b, key...)
		return hashers.FnvUint64(b)
	}
	return 0
}

// PartialKey computes the unique hash of the event
// that can be employed to determine if the event
// from the given process and source has been processed
// in the rule sequences.
func (e *Event) PartialKey() uint64 {
	switch e.Type {
	case WriteFile, ReadFile:
		return e.Params.MustGetUint64(params.FileObject) + uint64(e.PID)
	case MapViewFile, UnmapViewFile:
		return e.Params.MustGetUint64(params.FileViewBase) + uint64(e.PID)
	case CreateFile:
		file, _ := e.Params.GetString(params.FilePath)
		b := make([]byte, 4+len(file))
		binary.LittleEndian.PutUint32(b, e.PID)
		b = append(b, []byte(file)...)
		return hashers.FnvUint64(b)
	case OpenProcess:
		pid := e.Params.MustGetUint32(params.ProcessID)
		access := e.Params.MustGetUint32(params.DesiredAccess)
		return uint64(pid + access + e.PID)
	case OpenThread:
		tid := e.Params.MustGetUint32(params.ThreadID)
		access := e.Params.MustGetUint32(params.DesiredAccess)
		return uint64(tid + access + e.PID)
	case AcceptTCPv4, RecvTCPv4, RecvUDPv4:
		b := make([]byte, 10)
		ip, _ := e.Params.GetIP(params.NetSIP)
		port, _ := e.Params.GetUint16(params.NetSport)
		binary.LittleEndian.PutUint32(b, e.PID)
		binary.LittleEndian.PutUint32(b, binary.BigEndian.Uint32(ip.To4()))
		binary.LittleEndian.PutUint16(b, port)
		return hashers.FnvUint64(b)
	case AcceptTCPv6, RecvTCPv6, RecvUDPv6:
		b := make([]byte, 22)
		ip, _ := e.Params.GetIP(params.NetSIP)
		port, _ := e.Params.GetUint16(params.NetSport)
		binary.LittleEndian.PutUint32(b, e.PID)
		binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[0:8]))
		binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[8:16]))
		binary.LittleEndian.PutUint16(b, port)
		return hashers.FnvUint64(b)
	case ConnectTCPv4, SendTCPv4, SendUDPv4:
		b := make([]byte, 10)
		ip, _ := e.Params.GetIP(params.NetDIP)
		port, _ := e.Params.GetUint16(params.NetDport)
		binary.LittleEndian.PutUint32(b, e.PID)
		binary.LittleEndian.PutUint32(b, binary.BigEndian.Uint32(ip.To4()))
		binary.LittleEndian.PutUint16(b, port)
		return hashers.FnvUint64(b)
	case ConnectTCPv6, SendTCPv6, SendUDPv6:
		b := make([]byte, 22)
		ip, _ := e.Params.GetIP(params.NetDIP)
		port, _ := e.Params.GetUint16(params.NetDport)
		binary.LittleEndian.PutUint32(b, e.PID)
		binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[0:8]))
		binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[8:16]))
		binary.LittleEndian.PutUint16(b, port)
		return hashers.FnvUint64(b)
	case RegOpenKey, RegQueryKey, RegQueryValue,
		RegDeleteKey, RegDeleteValue, RegSetValue,
		RegCloseKey:
		key, _ := e.Params.GetString(params.RegPath)
		b := make([]byte, 4+len(key))
		binary.LittleEndian.PutUint32(b, e.PID)
		b = append(b, key...)
		return hashers.FnvUint64(b)
	case VirtualAlloc, VirtualFree:
		return e.Params.MustGetUint64(params.MemBaseAddress) + uint64(e.PID)
	case DuplicateHandle:
		pid := e.Params.MustGetUint32(params.ProcessID)
		object := e.Params.MustGetUint64(params.HandleObject)
		return object + uint64(pid+e.PID)
	case QueryDNS, ReplyDNS:
		n, _ := e.Params.GetString(params.DNSName)
		b := make([]byte, 4+len(n))
		binary.LittleEndian.PutUint32(b, e.PID)
		b = append(b, n...)
		return hashers.FnvUint64(b)
	}
	return 0
}

// BacklogKey represents the key used to index the events in the backlog store.
func (e *Event) BacklogKey() uint64 {
	switch e.Type {
	case CreateHandle, CloseHandle:
		return e.Params.MustGetUint64(params.HandleObject)
	}
	return 0
}

// CopyState adds parameters, tags, or process state from the provided event.
func (e *Event) CopyState(evt *Event) {
	switch evt.Type {
	case CloseHandle:
		if evt.Params.Contains(params.ImagePath) {
			e.Params.Append(params.ImagePath, params.UnicodeString, evt.GetParamAsString(params.ImagePath))
		}
		_ = e.Params.SetValue(params.HandleObjectName, evt.GetParamAsString(params.HandleObjectName))
	}
}

// Summary returns a brief summary of this event. Various important substrings
// in the summary text are highlighted by surrounding them inside <code> HTML tags.
func (e *Event) Summary() string {
	switch e.Type {
	case CreateProcess:
		exe := e.Params.MustGetString(params.Exe)
		sid := e.GetParamAsString(params.Username)
		return printSummary(e, fmt.Sprintf("spawned <code>%s</code> process as <code>%s</code> user", exe, sid))
	case TerminateProcess:
		exe := e.Params.MustGetString(params.Exe)
		sid := e.GetParamAsString(params.Username)
		return printSummary(e, fmt.Sprintf("terminated <code>%s</code> process as <code>%s</code> user", exe, sid))
	case OpenProcess:
		access := e.GetParamAsString(params.DesiredAccess)
		exe, _ := e.Params.GetString(params.Exe)
		return printSummary(e, fmt.Sprintf("opened <code>%s</code> process object with <code>%s</code> access right(s)",
			exe, access))
	case CreateThread:
		tid, _ := e.Params.GetTid()
		addr := e.GetParamAsString(params.StartAddress)
		return printSummary(e, fmt.Sprintf("spawned a new thread with <code>%d</code> id at <code>%s</code> address",
			tid, addr))
	case TerminateThread:
		tid, _ := e.Params.GetTid()
		addr := e.GetParamAsString(params.StartAddress)
		return printSummary(e, fmt.Sprintf("terminated a thread with <code>%d</code> id at <code>%s</code> address",
			tid, addr))
	case OpenThread:
		access := e.GetParamAsString(params.DesiredAccess)
		exe, _ := e.Params.GetString(params.Exe)
		return printSummary(e, fmt.Sprintf("opened <code>%s</code> process' thread object with <code>%s</code> access right(s)",
			exe, access))
	case LoadImage:
		filename := e.GetParamAsString(params.FilePath)
		return printSummary(e, fmt.Sprintf("loaded </code>%s</code> module", filename))
	case UnloadImage:
		filename := e.GetParamAsString(params.FilePath)
		return printSummary(e, fmt.Sprintf("unloaded </code>%s</code> module", filename))
	case CreateFile:
		op := e.GetParamAsString(params.FileOperation)
		filename := e.GetParamAsString(params.FilePath)
		return printSummary(e, fmt.Sprintf("%sed a file <code>%s</code>", strings.ToLower(op), filename))
	case ReadFile:
		filename := e.GetParamAsString(params.FilePath)
		size, _ := e.Params.GetUint32(params.FileIoSize)
		return printSummary(e, fmt.Sprintf("read <code>%d</code> bytes from <code>%s</code> file", size, filename))
	case WriteFile:
		filename := e.GetParamAsString(params.FilePath)
		size, _ := e.Params.GetUint32(params.FileIoSize)
		return printSummary(e, fmt.Sprintf("wrote <code>%d</code> bytes to <code>%s</code> file", size, filename))
	case SetFileInformation:
		filename := e.GetParamAsString(params.FilePath)
		class := e.GetParamAsString(params.FileInfoClass)
		return printSummary(e, fmt.Sprintf("set <code>%s</code> information class on <code>%s</code> file", class, filename))
	case DeleteFile:
		filename := e.GetParamAsString(params.FilePath)
		return printSummary(e, fmt.Sprintf("deleted <code>%s</code> file", filename))
	case RenameFile:
		filename := e.GetParamAsString(params.FilePath)
		return printSummary(e, fmt.Sprintf("renamed <code>%s</code> file", filename))
	case CloseFile:
		filename := e.GetParamAsString(params.FilePath)
		return printSummary(e, fmt.Sprintf("closed <code>%s</code> file", filename))
	case EnumDirectory:
		filename := e.GetParamAsString(params.FilePath)
		return printSummary(e, fmt.Sprintf("enumerated <code>%s</code> directory", filename))
	case RegCreateKey:
		key := e.GetParamAsString(params.RegPath)
		return printSummary(e, fmt.Sprintf("created <code>%s</code> key", key))
	case RegOpenKey:
		key := e.GetParamAsString(params.RegPath)
		return printSummary(e, fmt.Sprintf("opened <code>%s</code> key", key))
	case RegDeleteKey:
		key := e.GetParamAsString(params.RegPath)
		return printSummary(e, fmt.Sprintf("deleted <code>%s</code> key", key))
	case RegQueryKey:
		key := e.GetParamAsString(params.RegPath)
		return printSummary(e, fmt.Sprintf("queried <code>%s</code> key", key))
	case RegSetValue:
		key := e.GetParamAsString(params.RegPath)
		val, err := e.Params.GetString(params.RegValue)
		if err != nil {
			return printSummary(e, fmt.Sprintf("set <code>%s</code> value", key))
		}
		return printSummary(e, fmt.Sprintf("set <code>%s</code> payload in <code>%s</code> value", val, key))
	case RegDeleteValue:
		key := e.GetParamAsString(params.RegPath)
		return printSummary(e, fmt.Sprintf("deleted <code>%s</code> value", key))
	case RegQueryValue:
		key := e.GetParamAsString(params.RegPath)
		return printSummary(e, fmt.Sprintf("queried <code>%s</code> value", key))
	case AcceptTCPv4, AcceptTCPv6:
		ip, _ := e.Params.GetIP(params.NetSIP)
		port, _ := e.Params.GetUint16(params.NetSport)
		return printSummary(e, fmt.Sprintf("accepted connection from <code>%v</code> and <code>%d</code> port", ip, port))
	case ConnectTCPv4, ConnectTCPv6:
		ip, _ := e.Params.GetIP(params.NetDIP)
		port, _ := e.Params.GetUint16(params.NetDport)
		return printSummary(e, fmt.Sprintf("connected to <code>%v</code> and <code>%d</code> port", ip, port))
	case SendTCPv4, SendTCPv6, SendUDPv4, SendUDPv6:
		ip, _ := e.Params.GetIP(params.NetDIP)
		port, _ := e.Params.GetUint16(params.NetDport)
		size, _ := e.Params.GetUint32(params.NetSize)
		return printSummary(e, fmt.Sprintf("sent <code>%d</code> bytes to <code>%v</code> and <code>%d</code> port",
			size, ip, port))
	case RecvTCPv4, RecvTCPv6, RecvUDPv4, RecvUDPv6:
		ip, _ := e.Params.GetIP(params.NetSIP)
		port, _ := e.Params.GetUint16(params.NetSport)
		size, _ := e.Params.GetUint32(params.NetSize)
		return printSummary(e, fmt.Sprintf("received <code>%d</code> bytes from <code>%v</code> and <code>%d</code> port",
			size, ip, port))
	case CreateHandle:
		handleType := e.GetParamAsString(params.HandleObjectTypeID)
		handleName := e.GetParamAsString(params.HandleObjectName)
		return printSummary(e, fmt.Sprintf("created <code>%s</code> handle of <code>%s</code> type",
			handleName, handleType))
	case CloseHandle:
		handleType := e.GetParamAsString(params.HandleObjectTypeID)
		handleName := e.GetParamAsString(params.HandleObjectName)
		return printSummary(e, fmt.Sprintf("closed <code>%s</code> handle of <code>%s</code> type",
			handleName, handleType))
	case VirtualAlloc:
		addr := e.GetParamAsString(params.MemBaseAddress)
		return printSummary(e, fmt.Sprintf("allocated memory at <code>%s</code> address", addr))
	case VirtualFree:
		addr := e.GetParamAsString(params.MemBaseAddress)
		return printSummary(e, fmt.Sprintf("released memory at <code>%s</code> address", addr))
	case MapViewFile:
		sec := e.GetParamAsString(params.FileViewSectionType)
		return printSummary(e, fmt.Sprintf("mapped view of <code>%s</code> section", sec))
	case UnmapViewFile:
		sec := e.GetParamAsString(params.FileViewSectionType)
		return printSummary(e, fmt.Sprintf("unmapped view of <code>%s</code> section", sec))
	case DuplicateHandle:
		handleType := e.GetParamAsString(params.HandleObjectTypeID)
		return printSummary(e, fmt.Sprintf("duplicated <code>%s</code> handle", handleType))
	case QueryDNS:
		dnsName := e.GetParamAsString(params.DNSName)
		return printSummary(e, fmt.Sprintf("sent <code>%s</code> DNS query", dnsName))
	case ReplyDNS:
		dnsName := e.GetParamAsString(params.DNSName)
		return printSummary(e, fmt.Sprintf("received DNS response for <code>%s</code> query", dnsName))
	case CreateSymbolicLinkObject:
		src := e.GetParamAsString(params.LinkSource)
		target := e.GetParamAsString(params.LinkTarget)
		return printSummary(e, fmt.Sprintf("created symbolic link from %s to %s", src, target))
	case SubmitThreadpoolWork:
		return printSummary(e, "enqueued the work item to the thread pool")
	case SubmitThreadpoolCallback:
		return printSummary(e, "Submitted the thread pool callback for execution within the work item")
	case SetThreadpoolTimer:
		return printSummary(e, "set thread pool timer object")
	}
	return ""
}

func printSummary(e *Event, text string) string {
	ps := e.PS
	if ps != nil {
		return fmt.Sprintf("<code>%s</code> %s", ps.Name, text)
	}
	return fmt.Sprintf("process with <code>%d</code> id %s", e.PID, text)
}
